package gologv0

import (
	"bytes"
	"fmt"
	"gitee.com/ymofen/gobase"
	"gitee.com/ymofen/gobase/gotask"
	"gitee.com/ymofen/gobase/gotask/grouptask"
	"io"
	"os"
	"path"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

// Known format codes:
// %T - 2006-01-02 15:04:05 local
// %t - 06-01-02 15:04:05 local

// %U - 2006-01-02 15:04:05 utc
// %u - 06-01-02 15:04:05 utc

// %L - Level (int)
// %S - Source
// %M - Message
// %P - typestr

// Ignores unknown formats
// Recommended: "[%D %T] [%L] (%S) %M"
func FormatLogRecord(format string, rec *GoLogRecord) []byte {
	if rec == nil {
		return nil
	}

	if len(format) == 0 {
		return nil
	}

	// Split the string into pieces by % signs
	pieces := bytes.Split([]byte(format), []byte{'%'})

	out := bytes.NewBuffer(make([]byte, 0, 64))

	// Iterate over the pieces, replacing known formats
	for i, piece := range pieces {
		if i > 0 && len(piece) > 0 {
			switch piece[0] {
			case 'T':
				out.WriteString(rec.CreatedTime.Format("2006-01-02 15:04:05"))
			case 't':
				out.WriteString(rec.CreatedTime.Format("06-01-02 15:04:05"))
			case 'U':
				out.WriteString(rec.CreatedTime.UTC().Format("2006-01-02 15:04:05"))
			case 'u':
				out.WriteString(rec.CreatedTime.UTC().Format("06-01-02 15:04:05"))
			case 'P':
				out.WriteString(rec.Typestr)
			case 'L':
				out.WriteString(fmt.Sprintf("%d", rec.Level))
			case 'S':
				out.WriteString(rec.Source)
			case 's':
				slice := strings.Split(rec.Source, "/")
				s := slice[len(slice)-1]
				out.WriteString(s)
			case 'M':
				out.WriteString(fmt.Sprintf("%v", rec.Msg))
			}
			if len(piece) > 1 {
				out.Write(piece[1:])
			}
		} else if len(piece) > 0 {
			out.Write(piece)
		}
	}
	return out.Bytes()
}

// 打印到控制台
func ConsoleLogMsg(rec *GoLogRecord) bool {
	//fmt.Sprintf("%s[%s:%d]: %v(%s)\r\n", DateTimeString3(rec.CreatedTime), rec.Typestr, rec.Level, rec.Msg, rec.Source)
	s := string(FormatLogRecord("%P:%L %u %M (%s)\r\n", rec))
	grouptask.DefaultGroupTask().PostTaskFunc("consolelog", func() {
		fmt.Fprint(os.Stdout, s)
	})
	return true
}

/*
重命名, 确保源文件存在
*/
func RenameLogFile(filename string) (newfile string, err error) {
	var tmpfile string
	for i := 0; i < 1000; i++ {
		if i == 0 {
			tmpfile = fmt.Sprintf("%s.%s", filename, gobase.DateYYYYMMDD(time.Now()))
		} else {
			tmpfile = fmt.Sprintf("%s.%s.%d", filename, gobase.DateYYYYMMDD(time.Now()), i)
		}
		if !gobase.FileIsExists(tmpfile) {
			err = os.Rename(filename, tmpfile)
			if err == nil {
				newfile = tmpfile
				return
			}
		}
	}
	return
}

type SingleCacheFileWriter struct {
	closedflag  int32
	chkflag     int32
	sessionid   string
	totalcnt    int // 总写入次数
	currentsize int
	maxsize     int
	filename    string
	msgfmt      string
	MsgLvl      int8 // 大于该值的lvl 才会记录(默认为:0)
	closeWg     sync.WaitGroup
	cachefile   *gobase.CacheFile
	taskchannel interface{}

	// 一个文件完成写入后事件
	OnFileEof func(filename string)
}

func NewSingleCacheFileWriter(filename string) *SingleCacheFileWriter {
	rval := &SingleCacheFileWriter{}
	rval.filename = filename
	rval.init()
	return rval
}

func (this *SingleCacheFileWriter) GetFileName() string {
	return this.filename
}

func (this *SingleCacheFileWriter) GetTotleCount() int {
	return this.totalcnt
}

func (this *SingleCacheFileWriter) SetMsgFmt(s string) {
	this.msgfmt = s
}

func (this *SingleCacheFileWriter) SetMaxSize(v int) {
	this.maxsize = v
}

func (this *SingleCacheFileWriter) init() {
	this.sessionid = fmt.Sprintf("single%p", this)
	this.msgfmt = "%P:%L %u %M (%s)\r\n"
	this.maxsize = 1024 * 1024 * 10
	grouptask.DefaultGroupTask().ConfigChannelMaxWorkNum(this.sessionid, 1)
	this.taskchannel = grouptask.DefaultGroupTask().CheckCreateFixedGroupChannel(this.sessionid)
	gobase.ForceCreateFilePath(this.filename)

	// 最少30s写一次文件
	gotask.DefaultPatrolTask().AddTask(this.sessionid, time.Second*30, func(id interface{}, args ...interface{}) {
		this.closeWg.Add(1)
		err := grouptask.DefaultGroupTask().PostChannelTaskFunc(this.taskchannel, this.onWriteMsg, "", func() {
			this.closeWg.Done()
		}, "")
		if err != nil {
			this.closeWg.Done()
		}
	})
}

/*
Close后, 实例不再使用
*/
func (this *SingleCacheFileWriter) Close() error {
	if !atomic.CompareAndSwapInt32(&this.closedflag, 0, 1) {
		return io.ErrClosedPipe
	}
	this.WaitIdle()
	gotask.DefaultPatrolTask().DelTask(this.sessionid)
	grouptask.DefaultGroupTask().RemoveFixedGroupChannel(this.sessionid)
	if this.cachefile != nil {
		this.cachefile.Close()
	}
	return nil
}

func (this *SingleCacheFileWriter) WaitIdle() {
	this.closeWg.Wait()
}

func (this *SingleCacheFileWriter) onWriteMsg(args ...interface{}) {
	if len(args) >= 2 {
		fn, _ := args[1].(func())
		defer fn()
	}

	//if !atomic.CompareAndSwapInt32(&this.chkflag, 0, 1) {
	//	panic("write sync err")
	//}
	//defer func() {
	//	atomic.StoreInt32(&this.chkflag, 0)
	//}()

	var buf []byte
	var ok bool
	if buf, ok = args[0].([]byte); !ok {
		s, _ := args[0].(string)
		buf = []byte(s)
	}

	if len(buf) == 0 {
		if this.cachefile != nil { // 写入到文件
			this.cachefile.Flush()
		}
		return
	}

	if this.cachefile == nil {
		if gobase.FileIsExists(this.filename) {
			_, err := RenameLogFile(this.filename)
			if err != nil {
				fmt.Fprintf(os.Stderr, "日志文件重命名失败:%s", err.Error())
			}
		}
		this.cachefile = gobase.NewCacheFile(this.filename, 2048)
		this.currentsize = 0
	} else {
		if this.currentsize+len(buf) > this.maxsize {
			this.cachefile.Close()
			if gobase.FileIsExists(this.cachefile.GetFileName()) {
				newfile, err := RenameLogFile(this.cachefile.GetFileName())
				if err != nil {
					fmt.Fprintf(os.Stderr, "日志文件重命名失败:%s", err.Error())
				} else {
					evt := this.OnFileEof
					if evt != nil {
						go evt(newfile)
					}
				}
			}
			this.cachefile = gobase.NewCacheFile(this.filename, 2048)
			this.currentsize = 0
		}
	}
	n, err := this.cachefile.Write(buf)
	if err != nil {
		fmt.Fprintf(os.Stderr, "写入缓存日志文件失败:%s", err.Error())
	} else {
		this.currentsize += n
		this.totalcnt++
	}
}

/*
false: 投递失败
true: 投递成功, done投递成功时会才会进行回调
*/
func (this *SingleCacheFileWriter) onWriteLogRecord(rec *GoLogRecord, done func()) bool {
	if atomic.LoadInt32(&this.closedflag) == 1 {
		return false
	}

	if rec.Level < this.MsgLvl {
		return false
	}

	s := FormatLogRecord(this.msgfmt, rec)

	err := grouptask.DefaultGroupTask().PostChannelTaskFunc(this.taskchannel, this.onWriteMsg, s, done)
	return err == nil
}

/*
AddLogger 函数
*/
func (this *SingleCacheFileWriter) OnLogMsg(rec *GoLogRecord) bool {
	this.closeWg.Add(1)
	r := this.onWriteLogRecord(rec, func() {
		this.closeWg.Done()
	})
	if !r {
		this.closeWg.Done()
	}
	return r
}

/*
直接写入数据
*/
func (this *SingleCacheFileWriter) WriteMsgf(msglvl int8, s string, args ...interface{}) bool {
	if atomic.LoadInt32(&this.closedflag) == 1 {
		return false
	}

	if msglvl < this.MsgLvl {
		return false
	}

	msg := s
	if len(msg) == 0 {
		msg = fmt.Sprint(args...)
	} else {
		if len(args) > 0 {
			msg = fmt.Sprintf(s, args...)
		}
	}

	this.closeWg.Add(1)
	err := grouptask.DefaultGroupTask().PostChannelTaskFunc(this.taskchannel, this.onWriteMsg, msg, func() {
		this.closeWg.Done()
	})
	if err != nil { // 投递失败
		this.closeWg.Done()
	}
	return err == nil
}

type CacheMultiFileWriter struct {
	closedflag int32
	maxsize    int
	msgfmt     string
	MsgLvl     int8 // 大于该值的lvl 才会记录(默认为:0)

	closeWg sync.WaitGroup

	filename string

	typestrLk  sync.RWMutex
	typestrMap map[string]*SingleCacheFileWriter

	// 一个文件完成写入后事件
	OnFileEof func(filename string)
}

func NewCacheMultiFileWriter(filename string) *CacheMultiFileWriter {
	rval := &CacheMultiFileWriter{}
	rval.filename = filename
	rval.init()
	return rval
}

func (this *CacheMultiFileWriter) SetMsgFmt(s string) {
	this.msgfmt = s
}

func (this *CacheMultiFileWriter) SetMaxSize(v int) {
	this.maxsize = v
}

func (this *CacheMultiFileWriter) init() {
	this.msgfmt = "%P:%L %u %M (%s)\r\n"
	this.maxsize = 1024 * 1024 * 10
	this.typestrMap = make(map[string]*SingleCacheFileWriter)
}

/*
Close后, 实例不再使用
*/
func (this *CacheMultiFileWriter) Close() error {
	if !atomic.CompareAndSwapInt32(&this.closedflag, 0, 1) {
		return io.ErrClosedPipe
	}

	this.typestrLk.Lock()
	for k, itm := range this.typestrMap {
		itm.Close()
		delete(this.typestrMap, k)
	}
	this.typestrLk.Unlock()
	return nil
}

func (this *CacheMultiFileWriter) WaitIdle() {
	this.closeWg.Wait()
}

func (this *CacheMultiFileWriter) onItemFileEof(filename string) {
	evt := this.OnFileEof
	if evt != nil {
		evt(filename)
	}
}

func (this *CacheMultiFileWriter) checkCreateType(typestr string) *SingleCacheFileWriter {
	this.typestrLk.RLock()
	itm := this.typestrMap[typestr]
	this.typestrLk.RUnlock()
	if itm == nil {
		this.typestrLk.Lock()
		defer this.typestrLk.Unlock()
		itm = this.typestrMap[typestr]
		if itm != nil {
			return itm
		}

		fileExt := path.Ext(this.filename)
		filename := fmt.Sprintf("%s-%s%s", strings.TrimSuffix(this.filename, fileExt), typestr, fileExt)
		itm = NewSingleCacheFileWriter(filename)
		itm.SetMsgFmt(this.msgfmt)
		itm.SetMaxSize(this.maxsize)
		itm.OnFileEof = this.onItemFileEof
		this.typestrMap[typestr] = itm
	}
	return itm
}

func (this *CacheMultiFileWriter) OnLogMsg(rec *GoLogRecord) bool {
	if atomic.LoadInt32(&this.closedflag) == 1 {
		return false
	}

	if rec.Level < this.MsgLvl {
		return false
	}

	this.closeWg.Add(1)
	itm := this.checkCreateType(rec.Typestr)
	r := itm.onWriteLogRecord(rec, func() {
		this.closeWg.Done()
	})
	if !r { // 投递失败
		this.closeWg.Done()
	}
	return r
}
